package org.simplextensions.registry.services;

import org.simplextensions.annotations.lifecycle.Add;
import org.simplextensions.annotations.lifecycle.Remove;
import org.simplextensions.annotations.lifecycle.Start;
import org.simplextensions.annotations.lifecycle.Stop;
import org.simplextensions.registry.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @author Tomasz Krzyzak, <a
 *         href="mailto:tomasz.krzyzak@gmail.com">tomasz.krzyzak@gmail.com</a>
 * @since 2009-07-29 21:58:02
 */
@org.simplextensions.annotations.ExtensionPoint(id = ServiceRegistry.EP_ID, extensionClass = {IServiceLocator.class})
public class ServiceRegistry extends ExtensionPointAdapter implements IServiceRegistry {

    public static final String EP_ID = "ServiceRegistry";

    public static final String SERVICE_INTERFACE = "serviceInterface";
    public static final String EXTENSION_TYPE = "extensionType";
    public static final String EXTENSION_TYPE_SERVICE_LOCATOR = "serviceLocator";
    public static final String EXTENSION_TYPE_SERVICE = "service";

    private static final Logger log = LoggerFactory.getLogger(ServiceRegistry.class);

    private Map<Class<?>, Extension> extensionsMap = new HashMap<Class<?>, Extension>();

    private Map<Class<?>, Object> servicesMap = new HashMap<Class<?>, Object>();
    private Map<IServiceLocator, Extension> serviceLocators = new HashMap<IServiceLocator, Extension>();

    public ServiceRegistry() {
        log.info("ServiceRegistry created...");
    }

    @Override
    public void start(ExtensionPoint ep) {
        super.start(ep);
        for (IServiceLocator sl : serviceLocators.keySet()) {
            sl.start();
        }
    }

    @Add
    public void addExtension(Extension extension) {
        Class<?> clazz = extension.getClazz();

        String extensionType = extension.getPropertyValue(EXTENSION_TYPE).getStringValue();
        if (EXTENSION_TYPE_SERVICE.equals(extensionType)) {

            PropertyValue serviceInterfaceParam = extension.getPropertyValue(SERVICE_INTERFACE);
            if (serviceInterfaceParam == null || serviceInterfaceParam.getClassValue() == null) {
                throw new IllegalStateException(extension.getId() + " does not define parameter: " + SERVICE_INTERFACE);
            }
            Class<?> serviceInterface = serviceInterfaceParam.getClassValue();
            if (!serviceInterface.isAssignableFrom(extension.getClazz())) {
                throw new IllegalStateException("service: " + extension.getId() + " does not implement defined interface: "
                        + serviceInterfaceParam.getClassValue().getCanonicalName());
            }

            if (serviceInterface != null) {
                log.debug("registering service: " + extension.getId() + " binded to iface: " + serviceInterface.getCanonicalName());
                extensionsMap.put(serviceInterface, extension);
            }
        } else if (EXTENSION_TYPE_SERVICE_LOCATOR.equals(extensionType) || IServiceLocator.class.isAssignableFrom(extension.getClass())) {

            serviceLocators.put((IServiceLocator) extension.getExecutable(), extension);

        }
    }

    @Remove
    public void removeExtension(Extension extension) {
        String extensionType = extension.getPropertyValue(EXTENSION_TYPE).getStringValue();
        if (EXTENSION_TYPE_SERVICE_LOCATOR.equals(extensionType)) {

        } else if (EXTENSION_TYPE_SERVICE.equals(extensionType)) {

            Iterator<Entry<Class<?>, Extension>> iterator = extensionsMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<Class<?>, Extension> e = iterator.next();
                if (e.getValue() == extension) {
                    Object service = servicesMap.remove(e.getKey());
                    if (service != null)
                        stopService(service);
                    iterator.remove();
                    return;
                }
            }
        }
    }

    @Override
    public void stop(ExtensionPoint ep) {
        super.stop(ep);

        for (Entry<Class<?>, Object> e : servicesMap.entrySet()) {
            try {
                stopService(e.getValue());
            } catch (Exception ex) {
                log.error("", ex);
            }

        }
        for (Entry<IServiceLocator, Extension> e : serviceLocators.entrySet()) {
            e.getKey().stop();
        }
    }

    /*
      * (non-Javadoc)
      *
      * @see
      * org.simplextensions.core.IIServiceRegistry#getService(java.lang.Class)
      */
    @SuppressWarnings("unchecked")
    public <T> T getService(Class<T> serviceInterface) {
        if (!isStarted())
            throw new IllegalStateException("Not yet started");

        Object service = servicesMap.get(serviceInterface);
        if (service == null) {
            Extension extension = extensionsMap.get(serviceInterface);
            if (extension != null) {
                service = extension.getExecutable();
                servicesMap.put(serviceInterface, service);
            }
        }
        if (service != null) {
            startService(service);
        } else {
            for (Entry<IServiceLocator, Extension> entry : serviceLocators.entrySet()) {
                T result = entry.getKey().getService(serviceInterface);
                if (result != null) {
                    return result;
                }
            }
        }
        return (T) service;
    }


    private void stopService(Object service) {
        Class<? extends Object> serviceClass = service.getClass();
        do {
            for (Method m : serviceClass.getDeclaredMethods()) {
                Stop annotation = m.getAnnotation(Stop.class);
                if (annotation != null) {
                    if (Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length > 0) {
                        throw new RuntimeException("stop method must be public and have no arguments");
                    }
                    try {
                        m.invoke(service);
                        return;
                    } catch (Exception e) {
                        log.warn("couldn't stop service: " + service, e);
                    }
                }
            }
            serviceClass = serviceClass.getSuperclass();
        } while (serviceClass != null);
    }

    private void startService(Object service) {
        Class<? extends Object> serviceClass = service.getClass();
        do {
            for (Method m : serviceClass.getDeclaredMethods()) {
                Start annotation = m.getAnnotation(Start.class);
                if (annotation != null) {
                    if (Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length > 0) {
                        throw new RuntimeException("start method must be public and have no arguments");
                    }
                    try {
                        m.invoke(service);
                        return;
                    } catch (Exception e) {
                        log.warn("couldn't start service: " + service, e);
                    }
                }
            }
            serviceClass = serviceClass.getSuperclass();
        } while (serviceClass != null);
    }

}
